﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Macro.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Defines the Macro type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.Saf.Engine
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;

    using Collaboris.Saf.Engine.DynamicVariables;
    using Collaboris.Saf.Engine.Report;
    using Collaboris.Saf.Exceptions;
    using Collaboris.Saf.Tracing;
    using Collaboris.Saf.Utilities;
    using Collaboris.Utils;

    /// <summary>
    /// Macro that creates action dynamically when needed
    /// </summary>
    public class Macro : IMacro, IEnumerable<IAction>
    {
        #region Constants and Fields

        /// <summary>
        /// The action list.
        /// </summary>
        private readonly SortedDictionary<int, IAction> actionList = new SortedDictionary<int, IAction>();

        /// <summary>
        /// The action names.
        /// </summary>
        private readonly SortedDictionary<int, string> actionIds = new SortedDictionary<int, string>();

        /// <summary>
        /// Indicates if this macro will create the actions dynamically via a factory object
        /// </summary>
        private readonly bool dynamicCreation = false;

        /// <summary>
        /// id.
        /// </summary>
        private readonly Guid templateId;

        /// <summary>
        /// Stores the factory instance used by this macro to create actions
        /// </summary>
        private ISafFactory actionFactory;

        /// <summary>
        /// The auto undo.
        /// </summary>
        private bool autoUndo;

        /// <summary>
        /// The current context.
        /// </summary>
        private SafContext currentContext;

        /// <summary>
        /// The do condition.
        /// </summary>
        private bool doCondition = true;

        /// <summary>
        /// Stores an instance of the object responsible for resolving the values of dynamic variables
        /// </summary>
        private DynamicVariableResolver dynamicResolver;

        /// <summary>
        /// Action Name
        /// </summary>
        private string name;

        /// <summary>
        /// Instance of the tracehelper used by this object
        /// </summary>
        private TraceHelper trace;

        /// <summary>
        /// The undo condition.
        /// </summary>
        private bool undoCondition = true;

        private Guid instanceId;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Macro"/> class.
        /// </summary>
        /// <param name="actionNames">
        /// The action names.
        /// </param>
        /// <param name="templateId">
        /// The templateId.
        /// </param>
        public Macro(List<string> actionNames, Guid templateId, Guid instanceId)
        {
            ValidationHelper.VerifyObjectArgument(actionNames, "actionNames");
            ValidationHelper.VerifyGuidArgument(templateId, "templateId");
            ValidationHelper.VerifyGuidArgument(instanceId, "instanceId");

            this.dynamicCreation = true;
            this.AddActionIds(actionNames);
            this.templateId = templateId;
            this.instanceId = instanceId;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Macro"/> class.
        /// </summary>
        /// <param name="actionNames">
        /// The action names.
        /// </param>
        /// <param name="factory">
        /// The factory.
        /// </param>
        /// <param name="templateId">
        /// The templateId.
        /// </param>
        public Macro(List<string> actionNames, ISafFactory factory, Guid templateId, Guid instanceId)
        {
            ValidationHelper.VerifyObjectArgument(actionNames, "actionNames");
            ValidationHelper.VerifyObjectArgument(factory, "factory");
            ValidationHelper.VerifyGuidArgument(templateId, "templateId");
            ValidationHelper.VerifyGuidArgument(instanceId, "instanceId");

            this.dynamicCreation = true;
            this.AddActionIds(actionNames);
            this.ActionFactory = factory;
            this.templateId = templateId;
            this.instanceId = instanceId;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Macro"/> class.
        /// </summary>
        /// <param name="actions">
        /// The actions.
        /// </param>
        /// <param name="templateId">
        /// The templateId.
        /// </param>
        public Macro(List<IAction> actions, Guid templateId, Guid instanceId)
        {
            ValidationHelper.VerifyObjectArgument(actions, "actions");
            ValidationHelper.VerifyGuidArgument(templateId, "templateId");
            ValidationHelper.VerifyGuidArgument(instanceId, "instanceId");

            this.dynamicCreation = false;
            this.templateId = templateId;
            this.instanceId = instanceId;
            this.AddActions(actions);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Macro"/> class.
        /// </summary>
        /// <param name="actionNames">
        /// The action names.
        /// </param>
        /// <param name="templateId">
        /// The templateId.
        /// </param>
        public Macro(List<string> actionNames, Guid templateId)
            : this(actionNames, templateId, Guid.NewGuid())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Macro"/> class.
        /// </summary>
        /// <param name="actionNames">
        /// The action names.
        /// </param>
        /// <param name="factory">
        /// The factory.
        /// </param>
        /// <param name="templateId">
        /// The templateId.
        /// </param>
        public Macro(List<string> actionNames, ISafFactory factory, Guid templateId)
            : this(actionNames, factory, templateId, Guid.NewGuid())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Macro"/> class.
        /// </summary>
        /// <param name="actions">
        /// The actions.
        /// </param>
        /// <param name="templateId">
        /// The templateId.
        /// </param>
        public Macro(List<IAction> actions, Guid templateId)
            : this(actions, templateId, Guid.NewGuid())
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the total number of actions assigned to the Macro. 
        /// </summary>
        /// <returns>
        /// </returns>
        public int ActionCount
        {
            get
            {
                if (this.actionIds.Count != 0)
                {
                    return this.actionIds.Count;
                }

                return this.actionList.Count;
            }
        }

        /// <summary>
        /// Gets the action properties.
        /// </summary>
        /// <value>The action properties.</value>
        public SerializableObjectDictionary<string> ActionProperties
        {
            get
            {
                if (this.currentContext != null)
                {
                    return this.currentContext.ActionProperties;
                }

                return null;
            }
        }

        /// <summary>
        /// Gets the context properties.
        /// </summary>
        /// <value>The context properties.</value>
        public SerializableObjectDictionary<string> ContextProperties
        {
            get
            {
                if (this.currentContext != null)
                {
                    return this.currentContext.ContextProperties;
                }

                return null;
            }
        }

        /// <summary>
        /// Gets or sets DynamicResolver.
        /// </summary>
        public DynamicVariableResolver DynamicResolver
        {
            get
            {
                return this.dynamicResolver;
            }

            set
            {
                this.dynamicResolver = value;
            }
        }

        /// <summary>
        /// Gets or sets the Macro name.
        /// </summary>
        /// <value>The name.</value>
        public string Name
        {
            get
            {
                if (string.IsNullOrEmpty(this.name))
                {
                    this.name = this.GetType().FullName;
                }

                return this.name;
            }

            set
            {
                this.name = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [undo condition].
        /// </summary>
        /// <value><c>true</c> if [undo condition]; otherwise, <c>false</c>.</value>
        public bool UndoCondition
        {
            get
            {
                return this.undoCondition;
            }

            set
            {
                this.undoCondition = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [do condition].
        /// </summary>
        /// <value><c>true</c> if [do condition]; otherwise, <c>false</c>.</value>
        protected bool DoCondition
        {
            get
            {
                return this.doCondition;
            }

            set
            {
                this.doCondition = value;
            }
        }

        /// <summary>
        /// Provides and instance of a Trace Helper
        /// </summary>
        protected TraceHelper Trace
        {
            get
            {
                if (this.trace == null)
                {
                    this.trace = new SafTraceHelper(this);
                }

                return this.trace;
            }
        }

        /// <summary>
        /// Gets the trace switch.
        /// </summary>
        /// <value>The trace switch.</value>
        private static TraceSwitch TraceSwitch
        {
            get
            {
                return SafTraceHelper.TraceSwitch;
            }
        }

        #endregion

        #region Implemented Interfaces

        #region IEnumerable

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region IEnumerable<IAction>

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        /// <filterpriority>1</filterpriority>
        public IEnumerator<IAction> GetEnumerator()
        {
            return this.actionList.Values.GetEnumerator();
        }

        #endregion

        #region IMacro

        /// <summary>
        /// Gets ot sets the action factory used by this macro to create actions
        /// </summary>
        /// <value>The action factory.</value>
        public ISafFactory ActionFactory
        {
            get
            {
                return this.actionFactory;
            }

            set
            {
                this.actionFactory = value;
            }
        }

        /// <summary>
        /// Determines if the macro should automatically 
        /// call the "UnDo" if an Exception is encountered
        /// during the "Do".
        /// </summary>
        /// <value><c>true</c> if [auto undo]; otherwise, <c>false</c>.</value>
        /// <remarks>
        /// Undo is only called on actions that have been processed.
        /// </remarks>
        public bool AutoUndo
        {
            get
            {
                return this.autoUndo;
            }

            set
            {
                this.autoUndo = value;
            }
        }

        /// <summary>
        /// Gets the ID for the Macro
        /// Template
        /// </summary>
        /// <value>The ID.</value>
        public Guid TemplateID
        {
            get
            {
                return this.templateId;
            }
        }

        /// <summary>
        /// Gets the instance id.
        /// </summary>
        /// <value>The instance id.</value>
         public Guid InstanceID
        {
            get
            {
                return this.instanceId;
            }
        }

        /// <summary>
        /// Gets the context.
        /// </summary>
        /// <value>The context.</value>
        public SafContext Context
        {
            get
            {
                return this.currentContext;
            }
        }

        /// <summary>
        /// Gets the <see cref="Collaboris.Saf.Engine.IAction"/> at the specified index.
        /// </summary>
        /// <value></value>
        public IDictionary<int, IAction> Actions
        {
            get
            {
                return this.actionList;

                // return this.GetAction(index);
            }
        }

        
        /// <summary>
        /// Gets the <see cref="Collaboris.Saf.Engine.IAction"/> at the specified index.
        /// </summary>
        /// <value></value>
        public IAction this[int index]
        {
            get
            {
                return this.actionList[index];

                // return this.GetAction(index);
            }
        }

        /// <summary>
        /// The do.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <returns>
        /// </returns>
        public MacroReport Do(SafContext context)
        {
            MacroReport macroReport = null;

            /*
            // if actions are created dynamically then ensure that there are no actions already created
            // (they will be dynamically created when needed)
            if (this.dynamicCreation)
            {
                this.ClearAllActions();
            }

            try
            {
                return this.InternalDo(context);
            }
            catch (ActionRunException exception)
            {
                TraceHelper.TraceException(TraceSwitch, "DynamicMacro", "Do", exception);

                if (this.AutoUndo)
                {
                    this.Undo(context);
                }

                throw;
            }
            */
            return this.InternalDo(context);
        }

        /// <summary>
        /// The undo.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <returns>
        /// </returns>
        public MacroReport Undo(SafContext context)
        {
            /*
            // if actions are created dynamically then ensure that there are no actions already created
            // (they will be dynamically created when needed)
            if (this.dynamicCreation)
            {
                this.ClearAllActions();
            }
            */
            return this.InternalUndo(context);
        }

        /// <summary>
        /// Gets a dynamic Variable matching the key.
        /// </summary>
        /// <param name="variableName">Name of the variable.</param>
        /// <returns></returns>
        public object GetDynamicVariable(string variableName)
        {
            if (this.DynamicResolver.PublishedVariables.ContainsKey(variableName))
                return this.DynamicResolver.PublishedVariables[variableName];
            
            return null;
        }

        #endregion

        #endregion

        #region Helper Methods

        /// <summary>
        /// Adds the IAction to the end of the IMacro instance.
        /// </summary>
        /// <param name="index">
        /// The index.
        /// </param>
        /// <param name="action">
        /// IAction to be added to.
        /// </param>
        private void AddAction(int index, IAction action)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "Macro", "AddAction");
            ValidationHelper.VerifyObjectArgument(action, "action");
            ValidationHelper.VerifyStringArgument(action.Name, "action.Name");

            if (action == null)
            {
                throw new ArgumentNullException("action", "Action cannot be Null");
            }

            // int newIndex = this.actionList.Count;

            // Add the action ot the actions list
            this.actionList.Add(index, action);

            this.InitialiseActionProperties(index, action);

            // Add the action to the names list if it's not already there
            if (!this.actionIds.ContainsKey(index))
            {
                this.actionIds.Add(index, action.ID.ToString());
            }                        

            TraceHelper.TraceMethodEnd(TraceSwitch, "Macro", "AddAction");
        }

        /// <summary>
        /// Adds the action names 
        /// </summary>
        /// <param name="actionIds">
        /// The action names.
        /// </param>
        private void AddActionIds(IEnumerable<string> actionIds)
        {
            foreach (string actionId in actionIds)
            {
                this.actionIds.Add(this.actionIds.Count, actionId);
            }
        }

        /// <summary>
        /// Adds an <see cref="IList{T}"/> of actions to the current
        /// Macro.
        /// </summary>
        /// <param name="actions">
        /// IList containing Actions
        /// </param>
        /// <remarks>
        /// This method should only be called to add actions to an empty collection 
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// </exception>
        private void AddActions(IList<IAction> actions)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "Macro", "AddActions");

            if (actions == null)
            {
                throw new ArgumentNullException("actions", "Action cannot be Null");
            }

            foreach (IAction action in actions)
            {
                /*
                // Get the index of the action we are about to add
                // int newIndex = action.Index;

                // If Index has not been initialised then take the counter as the index
                // Note: This is dangerous because there could be actions with the same index already there
                if (newIndex == 0)
                {
                    newIndex = this.actionList.Count;
                }

                this.AddAction(newIndex, action);
                */
                this.AddAction(this.actionList.Count, action);
            }

            TraceHelper.TraceMethodEnd(TraceSwitch, "Macro", "AddActions");
        }

        /*
        /// <summary>
        /// The clear all actions.
        /// </summary>
        private void ClearAllActions()
        {
            this.actionList.Clear();

            if (this.DynamicResolver != null)
            {
                this.DynamicResolver.ClearObjectInstances();
            }
        }
        */

        /// <summary>
        /// Executes the do currentAction.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="currentAction">
        /// The action.
        /// </param>
        /// <returns>
        /// </returns>
        private ActionReport ExecuteDoAction(SafContext context, IAction currentAction)
        {
            ActionReport report;

            try
            {
                report = (ActionReport)currentAction.Do(context);
                
            }
            catch (ActionRunException ex)
            {
                this.Trace.TraceException("ProcessDoAction()", ex);
                
                if (currentAction.StopOnDoException)
                {
                    // This Denotes that we need to stop running 
                    // the macro, so lets fall out.
                    throw;
                }

                // if the Action Fails it wires its macroReport into the Excetion
                report = (ActionReport)ex.ProgressReport;

                this.Trace.TraceWarning("'StopOnDoException' was set to false, so continuing to process the macro");
            }

            this.Trace.TraceMethodEnd("MacroBase.ProcessDoAction()");
            return report;
        }

        /// <summary>
        /// Executes the undo action.
        /// </summary>
        /// <param name="context">
        /// The context.
        /// </param>
        /// <param name="currentAction">
        /// The current action.
        /// </param>
        /// <returns>
        /// </returns>
        private ActionReport ExecuteUndoAction(SafContext context, IAction currentAction)
        {
            this.Trace.TraceMethodStart("MacroBase.ProcessUndoAction()");
            ActionReport report;

            try
            {
                report = (ActionReport)currentAction.Undo(context);
            }
            catch (ActionRunException ex)
            {
                this.Trace.TraceException("ProcessUndoAction()", ex);

                if (currentAction.StopOnUndoException)
                {
                    // This Denotes that we need to stop running 
                    // the macro, so lets fall out.
                    throw;
                }

                // if the Action Fails it wires its macroReport into the Excetion
                report = (ActionReport)ex.ProgressReport;

                this.Trace.TraceWarning("'StopOnUndoException' was set to false, so continuing to process the macro");
            }

            this.Trace.TraceMethodEnd("MacroBase.ProcessUndoAction()");
            return report;
        }

        /// <summary>
        /// generate action id.
        /// </summary>
        /// <param name="macroId">
        /// The macro id.
        /// </param>
        /// <param name="index">
        /// The index.
        /// </param>
        /// <returns>
        /// </returns>
        private Guid GenerateActionId(Guid macroId, int index)
        {
            // The GUID index used by the macro to generate the action ID from the macro ID
            const int IndexTochange = 3;

            byte[] macroIdArray = macroId.ToByteArray();

            // Replace byte at the index specified by the indexTochange
            macroIdArray[IndexTochange] = (byte)index;

            var actionId = new Guid(macroIdArray);

            return actionId;
        }

        /*
        /// <summary>
        /// The get action.
        /// </summary>
        /// <param name="actionName">
        /// The action name.
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// </exception>
        private IAction GetAction(string actionName)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "Macro", "GetAction");

            try
            {
                if (!this.actionNames.ContainsValue(actionName))
                {
                    throw new ArgumentOutOfRangeException(
                        actionName, "Action with name '" + actionName + "' could not be found");
                }

                foreach (int index in this.actionNames.Keys)
                {
                    if (actionName == this.actionNames[index])
                    {
                        return this.GetAction(index);
                    }
                }

                // It should never get here
                return null;
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "Macro", "GetAction");
            }
        }
        */

        /// <summary>
        /// Gets an action. It returns the action if it already exists.
        /// If the action being requested is has not yet been created then it dynamically creates and returns a new action.
        /// </summary>
        /// <param name="index">
        /// The index.
        /// </param>
        /// <returns>
        /// </returns>
        private IAction GetAction(int index)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "Macro", "GetAction");

            IAction action = null;

            try
            {
                if (this.actionList.ContainsKey(index))
                {
                    action = this.actionList[index];
                }
                else
                {
                    // If we cannot get the action name then we cannot create it
                    if (!this.actionIds.ContainsKey(index))
                    {
                        throw new IndexOutOfRangeException(
                            "Name for action with index '" + index + "' could not be found");
                    }

                    // If factory has not been created then return an error
                    if (this.ActionFactory == null)
                    {
                        throw new NullReferenceException("Action factory class has not been initialised");
                    }

                    // Create the action
                    action = this.ActionFactory.CreateAction(this.actionIds[index]);
                    if (action != null)
                    {
                        // this.actionList.Add(index, action);
                        // Add the action to the actions list
                        this.AddAction(index, action);
                    }
                }
            }
            finally
            {
                TraceHelper.TraceMethodEnd(TraceSwitch, "Macro", "GetAction");
            }

            // Add the variables resolver
            if (this.DynamicResolver != null)
            {
                action.RefreshDynamicVariables = this.DynamicResolver.Resolve;
            }

            return action;
        }

        /// <summary>
        /// Initialises the action properties.
        /// </summary>
        /// <param name="index">
        /// The index.
        /// </param>
        /// <param name="action">
        /// The action.
        /// </param>
        private void InitialiseActionProperties(int index, IAction action)
        {
            ValidationHelper.VerifyObjectArgument(action, "action");

            action.Index = index;

            // If action does not implement it's own ID then generate one based on the macro ID and the action index
            if (this.TemplateID != Guid.Empty && action.ID == Guid.Empty)
            {
                action.ID = this.GenerateActionId(this.TemplateID, index);
            }
        }

        /// <summary>
        /// Reponsible calling 'Do' on all the IAction's
        /// in the macro. The IContext will passed from one
        /// Action to the other allowing a predecessor to pass
        /// data and communicate with the next Actions in the
        /// queue.
        /// </summary>
        /// <param name="context">
        /// </param>
        /// <returns>
        /// </returns>
        private MacroReport InternalDo(SafContext context)
        {
            this.Trace.TraceMethodStart("MacroBase.Do()");

            ValidationHelper.VerifyObjectArgument(context, "context");

            this.currentContext = context;

            MacroReport macroReport = null;
            int currentActionIndex = 0;
            ProcessStatus status = ProcessStatus.Success;

            try
            {
                // Create a Top Level Macro Report
                macroReport = ProgressReportUtils.StartReport<MacroReport>(ProcessMode.Do, this.Name, this.TemplateID);
                macroReport.InstanceId = this.instanceId;

                if (this.DoCondition)
                {
                    foreach (int actionIndex in this.actionIds.Keys)
                    {
                        // IAction currentAction = this.SafFactory.CreateAction(this.ActionNames[actionIdex]);
                        currentActionIndex = actionIndex;

                        IAction currentAction = this.GetAction(actionIndex);

                        ActionReport actionReport = this.ExecuteDoAction(context, currentAction);

                        actionReport.ContextProperties = context.ContextProperties;

                        // Finish Report and Add it to the Summary
                        macroReport.ActionReports.Add(actionReport);

                        // If the Action Returns False, lets quit processing.
                        if (actionReport.Status == ProcessStatus.Stop)
                        {
                            this.Trace.TraceWarning(
                                "A 'Stop' was received from the Action " + " at this position in the macro {0}",
                                (actionIndex + 1).ToString());
                            this.Trace.TraceMethodEnd("MacroBase.Do()");

                            return macroReport;
                        }
                    }
                }
                else
                {
                    // The DoCondition Evaluated to False
                    this.Trace.TraceWarning(
                        "The 'DoCondition' applied to this macro '{0}' was evaluated and returned false. This Action will be skipped.", 
                        this.Name);

                    status = ProcessStatus.Skip;
                }
            }
            catch (ActionRunException ex)
            {
                // if the Action Fails it wires its macroReport into the Excetion
                macroReport.ActionReports.Add((ActionReport)ex.ProgressReport);

                // Replace the current Report with the newer one.
                ex.ProgressReport = macroReport;

                status = ProcessStatus.Error;

                // One of our Actions has error'd - so what do we do ?
                this.Trace.TraceException("MacroBase.Do()", ex);
                this.Trace.TraceWarning(
                    "An Exception was received (and logged) in an " + " Action at this position '{0}' in the Macro.",
                    currentActionIndex.ToString());

                throw;
            }
            finally
            {
                if (macroReport != null)
                {
                    // save the Action Properties
                    macroReport.UserProperties = context.ActionProperties;

                    ProgressReportUtils.FinishReport<MacroReport>(macroReport, status);

                    context.LastDoReport = macroReport;
                }

                this.Trace.TraceMethodEnd("MacroBase.Do()");
            }

            return macroReport;
        }

        /// <summary>
        /// Reponsible calling 'Undo' on all the IAction's
        /// in the macro. The IContext will passed from one
        /// Action to the other allowing a predecessor to pass
        /// data and communicate with the next Actions in the
        /// queue.
        /// </summary>
        /// <param name="context">
        /// </param>
        /// <returns>
        /// </returns>
        private MacroReport InternalUndo(SafContext context)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "Macro", "UnDo");

            // Get the list of actions completed
            List<int> actionsCompleted = null;
            MacroReport lastDoReport = context.LastDoReport;

            if (lastDoReport != null)
            {
                // The do has already ran on the macro so get the list of actions that ran
                actionsCompleted = lastDoReport.GetActionsCompleted();
            }
            else
            {
                actionsCompleted = new List<int>();

                // The macro never ran so assume that all actions will be undone
                IEnumerable<int> actionIndexes;

                if (this.dynamicCreation)
                    actionIndexes = this.actionIds.Keys;
                else
                    actionIndexes = this.Actions.Keys;

                foreach (int actionIndex in actionIndexes)
                {
                    actionsCompleted.Insert(0, actionIndex);
                }
            }

            MacroReport report = this.UndoMacro(context, actionsCompleted, lastDoReport, false);

            TraceHelper.TraceMethodEnd(TraceSwitch, "Macro", "UnDo");

            return report;
        }

        /// <summary>
        /// Calls Undo on each Action.
        /// </summary>
        /// <param name="context">
        /// IContext that should be processed
        /// </param>
        /// <param name="actionsToUndo">
        /// The keys of the actions to undo.
        /// </param>
        /// <param name="macroReport">
        /// The macro Report.
        /// </param>
        /// <param name="AutoUndoMode">
        /// The Auto Undo Mode.
        /// </param>
        /// <returns>
        /// IProgressReport
        /// </returns>
        private MacroReport UndoMacro(
            SafContext context, List<int> actionsToUndo, MacroReport macroReport, bool AutoUndoMode)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "Macro", "UndoMacro");

            ValidationHelper.VerifyObjectArgument(context, "context");
            ValidationHelper.VerifyObjectArgument(actionsToUndo, "actionsToUndo");

            this.currentContext = context;
            ProcessStatus status = ProcessStatus.Success;

            if (macroReport == null)
            {
                // Create a Top Level Macro Report
                macroReport = ProgressReportUtils.StartReport<MacroReport>(ProcessMode.Undo, this.Name, this.TemplateID);
                macroReport.InstanceId = this.instanceId;
            }

            int currentActionIndex = 0;
            // string currentActionName = String.Empty;

            try
            {
                if (this.UndoCondition)
                {
                    // If there are no actions to undo then return;
                    if (actionsToUndo.Count == 0)
                    {
                        TraceHelper.TraceWarning(
                            TraceSwitch, 
                            "Macro", 
                            "Undo was called on a macro that does not have any actions to undo");
                        return macroReport;
                    }

                    // Start at the end and decrement down
                    // for (macroIndex = numOfActions; macroIndex >= 0; macroIndex--)
                    foreach (int actionIndex in actionsToUndo)
                    {
                        //currentActionName = actionIndex;
                        currentActionIndex = actionIndex;

                        ////IAction currentAction = this.SafFactory.CreateAction(this.ActionNames[actionIndex]);
                        IAction currentAction = this.GetAction(actionIndex);

                        ActionReport actionReport = this.ExecuteUndoAction(context, currentAction);

                        actionReport.ContextProperties = context.ContextProperties;

                        // If the Action has DisableUndo set, then no macroReport is generated.
                        macroReport.ActionReports.Add(actionReport);

                        // If the Action Returns False, lets quit processing.
                        if (actionReport.Status == ProcessStatus.Stop)
                        {
                            this.Trace.TraceWarning("A 'Stop' was received from the Action with name {0}", actionIndex);
                            this.Trace.TraceMethodEnd("MacroBase.UndoMacro()");

                            return macroReport;
                        }
                    }
                }
                else
                {
                    // The UndoCondition Evaluated to False
                    this.Trace.TraceWarning(
                        "The 'DoCondition' applied to this macro '{0}' was evaluated and returned false. This Action will be skipped.", 
                        this.Name);

                    status = ProcessStatus.Skip;
                }
            }
            catch (ActionRunException ex)
            {
                // if the Action Fails it wires its macroReport into the Excetion
                macroReport.ActionReports.Add((ActionReport)ex.ProgressReport);

                // Replace the current Report with the newer one.
                ex.ProgressReport = macroReport;
                status = ProcessStatus.Error;

                // One of our Actions has error'd - so what do we do ?
                this.Trace.TraceException("MacroBase.UndoMacro()", ex);
                this.Trace.TraceWarning(
                    "An Exception was received (and logged) in an Action at" + " this position '{0}' in the Macro.", 
                    currentActionIndex);

                if (!AutoUndoMode)
                {
                    throw;
                }
            }
            finally
            {
                ProgressReportUtils.FinishReport<MacroReport>(macroReport, status);

                this.Trace.TraceMethodEnd("MacroBase.UndoMacro()");
            }

            return macroReport;
        }

        #endregion
    }
}